
package com.braintreepayments.cardform.view;

import com.braintreepayments.cardform.OnCardFormFieldFocusedListener;
import com.braintreepayments.cardform.OnCardFormSubmitListener;
import com.braintreepayments.cardform.ResourceTable;
import com.braintreepayments.cardform.utils.CardType;
import com.rengwuxian.materialedittext.MaterialEditText;
import com.rengwuxian.materialedittext.ResUtil;
import ohos.aafwk.ability.AbilitySlice;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Button;
import ohos.agp.components.Component;
import ohos.agp.components.DirectionalLayout;
import ohos.agp.components.Image;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.Text;
import ohos.agp.components.TextField;
import ohos.app.Context;

public class CardForm extends DirectionalLayout implements Text.TextObserver, Component.FocusChangedListener {

    /**
     * Hides the field.
     */
    public static final int FIELD_DISABLED = 0;

    /**
     * Shows the field, and makes the field optional.
     */
    public static final int FIELD_OPTIONAL = 1;

    /**
     * Shows the field, and require the field value to be non empty when validating the card form.
     */
    public static final int FIELD_REQUIRED = 2;
    private static final String WHITE_SPACE = " ";
    private static final char PAUSE = ',';
    private static final char WAIT = ';';
    private static final char WILD = 'N';
    private boolean mCardNumberRequired;
    private boolean mExpirationRequired;
    private boolean mCvvRequired;
    private int mCardholderNameStatus = FIELD_DISABLED;
    private boolean mPostalCodeRequired;
    private boolean mMobileNumberRequired;
    private boolean mSaveCardCheckBoxVisible;
    private boolean mSaveCardCheckBoxChecked;
    private OnCardFormSubmitListener mOnCardFormSubmitListener;
    private OnCardFormFieldFocusedListener mOnCardFormFieldFocusedListener;
    private OnCardTypeChangedListener mOnCardTypeChangedListener;
    private MaterialEditText mCardholderName;
    private MaterialEditText mCardNumber;
    private MaterialEditText mExpiration;
    private MaterialEditText mCvv;
    private MaterialEditText mPostalCode;
    private MaterialEditText mCountryCode;
    private MaterialEditText mMobileNumber;
    private Text mMobileNumberExplanation;
    private InitialValueCheckBox mSaveCardCheckBox;

    private int lengthBefore;
    private Button mFormSubmit;

    /**
     * 构造方法
     *
     * @param context 上下文
     */
    public CardForm(Context context) {
        super(context);
        init();
        setListeners();
        setFocusChangedListeners();
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrs AttrSet
     */
    public CardForm(Context context, AttrSet attrs) {
        super(context, attrs);
        init();
        setListeners();
        setFocusChangedListeners();
    }

    /**
     * 构造方法
     *
     * @param context 上下文
     * @param attrs AttrSet
     * @param defStyleAttr Style
     */
    public CardForm(Context context, AttrSet attrs, String defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
        setListeners();
        setFocusChangedListeners();
    }

    private void init() {
        LayoutScatter.getInstance(getContext()).parse(
                ResourceTable.Layout_bt_card_form_fields, this, true);
        mCardholderName = (MaterialEditText) findComponentById(ResourceTable.Id_bt_card_form_cardholder_name);
        mCardNumber = (MaterialEditText) findComponentById(ResourceTable.Id_bt_card_form_card_number);
        mExpiration = (MaterialEditText) findComponentById(ResourceTable.Id_bt_card_form_expiration);
        mCvv = (MaterialEditText) findComponentById(ResourceTable.Id_bt_card_form_cvv);
        mPostalCode = (MaterialEditText) findComponentById(ResourceTable.Id_bt_card_form_postal_code);
        mCountryCode = (MaterialEditText) findComponentById(ResourceTable.Id_bt_card_form_country_code);
        mMobileNumber = (MaterialEditText) findComponentById(ResourceTable.Id_bt_card_form_mobile_number);
        mMobileNumberExplanation = (Text) findComponentById(ResourceTable.Id_bt_card_form_mobile_number_explanation);
        mSaveCardCheckBox = (InitialValueCheckBox) findComponentById(ResourceTable.Id_bt_card_form_save_card_checkbox);
        mFormSubmit = (Button) findComponentById(ResourceTable.Id_btn_submit);
        formSubmit();
    }

    private void formSubmit() {
        mFormSubmit.setClickedListener(v -> {
            String holderName = mCardholderName.getText();
            if (holderName.isEmpty()) {
                mCardholderName.setErrorText("持卡人姓名为必填项");
            }
            String cardNumber = mCardNumber.getText();
            if (cardNumber.isEmpty()) {
                mCardNumber.setErrorText("卡号为必填项");
            } else {
                CardType type = CardType.forCardNumber(mCardNumber.getText().trim().replaceAll(WHITE_SPACE, ""));
                if (type == CardType.UNKNOWN || mCardNumber.getText().trim().length() < 23) {
                    mCardNumber.setErrorText("卡号无效");
                }
            }
            String expiration = mExpiration.getText();
            if (expiration.isEmpty()) {
                mExpiration.setErrorText("有效期限为必填项");
            } else {
                if (expiration.length() < 7) {
                    mExpiration.setErrorText("有效期限无效");
                }
            }
            String cvv = mCvv.getText();
            if (cvv.isEmpty()) {
                mCvv.setErrorText("CVV为必填项");
            }
            String postalCode = mPostalCode.getText();
            if (postalCode.isEmpty()) {
                mPostalCode.setErrorText("邮政编码为必填项");
            }
            String countryCode = mCountryCode.getText();
            if (countryCode.isEmpty()) {
                mCountryCode.setErrorText("国家或地区代码为必填项");
            }
            String mobileNumber = mMobileNumber.getText();
            if (mobileNumber.isEmpty()) {
                mMobileNumber.setErrorText("手机号码为必填项");
            }
            if (mOnCardFormSubmitListener != null) {
                mOnCardFormSubmitListener.onCardFormSubmit();
            }
        });
    }

    private void setListeners() {
        mCardholderName.setOnTextListener((text) -> {
            if (text.length() > 455) {
                mCardholderName.getTextField().setText(text.substring(0, 455));
            }
            if (text.isEmpty()) {
                mCardholderName.setValid(false);
            } else {
                mCardholderName.setValid(true);
            }
        });
        Image typeIcon = mCardNumber.getTypeIcon();
        mCardNumber.setOnTextListener((text) -> {
            if (text.length() > 23) {
                String format = format(text);
                mCardNumber.getTextField().setText(format.substring(0, 23));
            } else {
                String format = format(text);
                mCardNumber.getTextField().setText(format);
                mCardNumber.setErrorText("");
            }
            CardType type = CardType.forCardNumber(mCardNumber.getText().trim().replaceAll(WHITE_SPACE, ""));
            if (mOnCardTypeChangedListener != null) {
                mOnCardTypeChangedListener.onCardTypeChanged(type);
            }
            if (type == CardType.EMPTY) {
                typeIcon.setVisibility(HIDE);
                mCardNumber.setValid(false);
            } else if (type == CardType.UNKNOWN) {
                mCardNumber.setValid(false);
                typeIcon.setVisibility(VISIBLE);
                typeIcon.setImageAndDecodeBounds(type.getFrontResource());
            } else {
                mCardNumber.setValid(true);
                typeIcon.setVisibility(VISIBLE);
                typeIcon.setImageAndDecodeBounds(type.getFrontResource());
            }
            mCvv.setHintText(ResUtil.getString(mContext, type.getSecurityCodeName()));
        });
        mExpiration.setOnTextListener((text) -> {
            if (lengthBefore < text.length()) {
                if (text.length() > 7) {
                    String format = formatExpiration(text);
                    mExpiration.getTextField().setText(format.substring(0, 7));
                } else {
                    String format = formatExpiration(text);
                    mExpiration.getTextField().setText(format);
                }
            }
            if (text.length() > 7) {
                String format = formatExpiration(text);
                mExpiration.getTextField().setText(format.substring(0, 7));
            }
            lengthBefore = text.length();
            if (text.isEmpty() || text.length() < 7) {
                mExpiration.setValid(false);
            } else {
                mExpiration.setValid(true);
            }
        });
        mCvv.setOnTextListener((text) -> {
            CardType type = CardType.forCardNumber(mCardNumber.getText());
            if (type == CardType.AMEX) {
                if (text.length() > 4) {
                    mCvv.getTextField().setText(text.substring(0, 4));
                } else {
                    mCvv.getTextField().setText(text);
                }
            } else {
                if (text.length() > 3) {
                    mCvv.getTextField().setText(text.substring(0, 3));
                } else {
                    mCvv.getTextField().setText(text);
                }
            }
            if (text.isEmpty() || (type == CardType.AMEX && text.length() < 4)
                    || type != CardType.AMEX && text.length() < 3) {
                mCvv.setValid(false);
            } else {
                mCvv.setValid(true);
            }
        });
        mPostalCode.setOnTextListener((text) -> {
            if (text.length() > 16) {
                mPostalCode.getTextField().setText(text.substring(0, 16));
            } else {
                mPostalCode.getTextField().setText(text);
            }
            if (text.isEmpty()) {
                mPostalCode.setValid(false);
            } else {
                mPostalCode.setValid(true);
            }
        });
        mCountryCode.setOnTextListener((text) -> {
            if (text.length() > 4) {
                mCountryCode.getTextField().setText(text.substring(0, 4));
            } else {
                mCountryCode.getTextField().setText(text);
            }
            if (text.isEmpty()) {
                mCountryCode.setValid(false);
            } else {
                mCountryCode.setValid(true);
            }
        });
        mMobileNumber.setOnTextListener((text) -> {
            if (text.length() > 17) {
                String separators = formatMobile(text);
                mMobileNumber.getTextField().setText(separators.substring(0, 17));
            } else {
                String separators = formatMobile(text);
                mMobileNumber.getTextField().setText(separators);
            }
            if (text.isEmpty() || text.length() < 7) {
                mMobileNumber.setValid(false);
            } else {
                mMobileNumber.setValid(true);
            }
        });
    }

    private void setFocusChangedListeners() {
        mCardNumber.setFocusChangedListener(new MaterialEditText.OnFocusChangedListener() {
            @Override
            public void onFocusChanged(boolean isFocus) {
                String all = mCardNumber.getText().trim();
                if (!isFocus && !all.isEmpty() && all.length() != 23) {
                    mCardNumber.setErrorText("卡号无效");
                }
            }
        });
        mExpiration.setFocusChangedListener(new MaterialEditText.OnFocusChangedListener() {
            @Override
            public void onFocusChanged(boolean isFocus) {
                String all = mExpiration.getText().trim();
                if (!isFocus && !all.isEmpty() && all.length() < 7) {
                    mExpiration.setErrorText("有效期限无效");
                }
            }
        });
        mCvv.setFocusChangedListener(new MaterialEditText.OnFocusChangedListener() {
            @Override
            public void onFocusChanged(boolean isFocus) {
                String all = mCvv.getText().trim();
                if (!isFocus && !all.isEmpty()) {
                    CardType type = CardType.forCardNumber(mCardNumber.getText());
                    if (type == CardType.AMEX) {
                        if (all.length() != 4) {
                            mCvv.setErrorText(mCvv.getTextField().getHint() + "无效");
                        }
                    } else {
                        if (all.length() != 3) {
                            mCvv.setErrorText(mCvv.getTextField().getHint() + "无效");
                        }
                    }
                }
            }
        });
        mMobileNumber.setFocusChangedListener(new MaterialEditText.OnFocusChangedListener() {
            @Override
            public void onFocusChanged(boolean isFocus) {
                String all = mMobileNumber.getText().trim();
                if (!isFocus && !all.isEmpty() && all.length() < 7) {
                    mMobileNumber.setErrorText("手机号无效");
                }
            }
        });
    }

    /**
     * 格式化银行卡号
     *
     * @param editable String
     * @return String
     */
    private String format(String editable) {
        String str = editable.trim().replaceAll(WHITE_SPACE, "");
        int len = str.length();
        StringBuilder builder = new StringBuilder();
        for (int index = 0; index < len; index++) {
            builder.append(str.charAt(index));
            if (index == 3 || index == 7 || index == 11 || index == 15) {
                if (index != len - 1)
                    builder.append(WHITE_SPACE);
            }
        }
        return builder.toString();
    }

    /**
     * 格式化年月
     *
     * @param editable String
     * @return String
     */
    private String formatExpiration(String editable) {
        String str = editable.trim().replaceAll("/", "");
        if (str.length() == 1 && Character.getNumericValue(str.charAt(0)) >= 2) {
            str = "0" + str;
        }
        int len = str.length();
        StringBuilder builder = new StringBuilder();
        for (int index = 0; index < len; index++) {
            builder.append(str.charAt(index));
            if (index == 1) {
                builder.append("/");
            }
        }
        return builder.toString();
    }

    /**
     * 格式化手机号
     *
     * @param phone String
     * @return String
     */
    private String formatMobile(String phone) {
        String str = phone.trim().replaceAll(WHITE_SPACE, "");
        int len = str.length();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < len; i++) {
            builder.append(str.charAt(i));
            if (i == 2 || i == 6 || i == 10) {
                if (i != len - 1)
                    builder.append(WHITE_SPACE);
            }
        }
        return builder.toString();
    }

    /**
     * Set the listener to receive a callback when the {@link CardType} changes.
     *
     * @param listener to receive the callback
     */
    public void setOnCardTypeChangedListener(OnCardTypeChangedListener listener) {
        mOnCardTypeChangedListener = listener;
    }

    public interface OnCardTypeChangedListener {
        /**
         * onCardTypeChanged
         *
         * @param cardType CardType
         */
        void onCardTypeChanged(CardType cardType);
    }

    /**
     * {@code true} to show and require card number, {@code false}otherwise. Defaults to {@code false}.
     *
     * @param required true or false. Defaults false.
     * @return {@link CardForm} for method chaining
     */
    public CardForm cardRequired(boolean required) {
        mCardNumberRequired = required;
        return this;
    }

    /**
     * {@code true} to show and require an expiration date, {@code false} otherwise. Defaults to {@code false}.
     *
     * @param required true or false. Defaults false.
     * @return {@link CardForm} for method chaining
     */
    public CardForm expirationRequired(boolean required) {
        mExpirationRequired = required;
        return this;
    }

    /**
     * {@code true} to show and require a cvv, {@code false} otherwise. Defaults to {@code false}.
     *
     * @param required true or false. Defaults false.
     * @return {@link CardForm} for method chaining
     */
    public CardForm cvvRequired(boolean required) {
        mCvvRequired = required;
        return this;
    }

    /**
     * {@link CardForm#FIELD_DISABLED} to hide this field. This is the default option.
     * {@link CardForm#FIELD_OPTIONAL} to show this field but make it an optional field.
     * {@link CardForm#FIELD_REQUIRED} to show this field and make it required to validate the card form.
     *
     * @param cardHolderNameStatus can be one of the options.
     * @return {@link CardForm} for method chaining
     */
    public CardForm cardholderName(int cardHolderNameStatus) {
        mCardholderNameStatus = cardHolderNameStatus;
        return this;
    }

    /**
     * {@code true} to show and require a postal code, {@code false} otherwise.
     * Defaults to {@code false}.
     *
     * @param required true or false. Defaults false.
     * @return {@link CardForm} for method chaining
     */
    public CardForm postalCodeRequired(boolean required) {
        mPostalCodeRequired = required;
        return this;
    }

    /**
     * {@code true} to show and require a mobile number, {@code false} otherwise. Defaults to {@code false}.
     *
     * @param required true or false. Defaults false.
     * @return {@link CardForm} for method chaining
     */
    public CardForm mobileNumberRequired(boolean required) {
        mMobileNumberRequired = required;
        return this;
    }

    /**
     * {@link CardForm} for method chaining
     *
     * @param mobileNumberExplanation to display below the mobile number input
     * @return CardForm
     */
    public CardForm mobileNumberExplanation(String mobileNumberExplanation) {
        mMobileNumberExplanation.setText(mobileNumberExplanation);
        return this;
    }

    /**
     * Determines if the save card CheckBox should be shown. Defaults to hidden / {@code false}
     *
     * @param visible Determines if the save card CheckBox should be shown. Defaults to hidden / {@code false}
     * @return {@link CardForm} for method chaining
     */
    public CardForm saveCardCheckBoxVisible(boolean visible) {
        mSaveCardCheckBoxVisible = visible;
        return this;
    }

    /**
     * The default value for the Save Card CheckBox.
     *
     * @param checked The default value for the Save Card CheckBox.
     * @return {@link CardForm} for method chaining
     */
    public CardForm saveCardCheckBoxChecked(boolean checked) {
        mSaveCardCheckBoxChecked = checked;
        return this;
    }

    /**
     * Sets up the card form for display to the user using the values provided in {@link CardForm#cardRequired(boolean)},
     * {@link CardForm#expirationRequired(boolean)}, ect. If is not called,
     * the form will not be visible.
     *
     * @param activity Used to set to prevent screenshots
     */
    public void setup(AbilitySlice activity) {
        boolean cardHolderNameVisible = mCardholderNameStatus != FIELD_DISABLED;
        setFieldVisibility(mCardholderName, cardHolderNameVisible);
        setFieldVisibility(mCardNumber, mCardNumberRequired);
        setFieldVisibility(mExpiration, mExpirationRequired);
        setFieldVisibility(mCvv, mCvvRequired);
        setFieldVisibility(mPostalCode, mPostalCodeRequired);
        setFieldVisibility(mCountryCode, mMobileNumberRequired);
        setFieldVisibility(mMobileNumber, mMobileNumberRequired);
        setViewVisibility(mMobileNumberExplanation, mMobileNumberRequired);
        setViewVisibility(mSaveCardCheckBox, mSaveCardCheckBoxVisible);
        mSaveCardCheckBox.setInitiallyChecked(mSaveCardCheckBoxChecked);
        setVisibility(VISIBLE);
    }

    private void setViewVisibility(Component view, boolean visible) {
        view.setVisibility(visible ? VISIBLE : HIDE);
    }

    private void setFieldVisibility(MaterialEditText editText, boolean visible) {
        setViewVisibility(editText, visible);
    }

    /**
     * Set the listener to receive a callback when the card form should be submitted.
     * Triggered from a keyboard by a event
     *
     * @param listener to receive the callback
     */
    public void setOnCardFormSubmitListener(OnCardFormSubmitListener listener) {
        mOnCardFormSubmitListener = listener;
    }

    /**
     * Set the listener to receive a callback when a field is focused
     *
     * @param listener to receive the callback
     */
    public void setOnFormFieldFocusedListener(OnCardFormFieldFocusedListener listener) {
        mOnCardFormFieldFocusedListener = listener;
    }

    /**
     * Set fields as enabled or disabled
     *
     * @param enabled {@code true} to enable all required fields, {@code false} to disable all required fields
     */
    public void setEnabled(boolean enabled) {
        mCardholderName.setEnabled(enabled);
        mCardNumber.setEnabled(enabled);
        mExpiration.setEnabled(enabled);
        mCvv.setEnabled(enabled);
        mPostalCode.setEnabled(enabled);
        mMobileNumber.setEnabled(enabled);
    }

    /**
     * {@code true} if all require fields are valid, otherwise {@code false}
     *
     * @return @code true} if all require fields are valid, otherwise {@code false}
     */
    public boolean isValid() {
        return mCardholderName.getValid() && mCardNumber.getValid()
                && mExpiration.getValid() && mCvv.getValid() && mPostalCode.getValid() && mCountryCode.getValid() && mMobileNumber.getValid();
    }

    /**
     * view in the card form
     *
     * @return view in the card form
     */
    public MaterialEditText getCardholderNameEditText() {
        return mCardholderName;
    }

    /**
     * view in the card form
     *
     * @return view in the card form
     */
    public MaterialEditText getCardEditText() {
        return mCardNumber;
    }

    /**
     * view in the card form
     *
     * @return view in the card form
     */
    public MaterialEditText getExpirationDateEditText() {
        return mExpiration;
    }

    /**
     * view in the card form
     *
     * @return view in the card form
     */
    public MaterialEditText getCvvEditText() {
        return mCvv;
    }

    /**
     * view in the card form
     *
     * @return view in the card form
     */
    public MaterialEditText getPostalCodeEditText() {
        return mPostalCode;
    }

    /**
     * view in the card form
     *
     * @return view in the card form
     */
    public MaterialEditText getCountryCodeEditText() {
        return mCountryCode;
    }

    /**
     * view in the card form
     *
     * @return view in the card form
     */
    public MaterialEditText getMobileNumberEditText() {
        return mMobileNumber;
    }

    private void requestEditTextFocus(TextField editText) {
        editText.requestFocus();
    }

    /**
     * view in the card form
     *
     * @return the text in the cardholder name field
     */
    public String getCardholderName() {
        return mCardholderName.getText().toString();
    }

    /**
     * view in the card form
     *
     * @return the text in the card number field
     */
    public String getCardNumber() {
        return mCardNumber.getText().toString();
    }

    /**
     * view in the card form
     *
     * @return the 2-digit month, formatted with a leading zero if necessary from the expiration
     * field. If no month has been specified, an empty string is returned.
     */
    public String getExpirationMonth() {
        return mExpiration.getTextField().getText();
    }

    /**
     * view in the card form
     *
     * @return the 2- or 4-digit year depending on user input from the expiration field.
     * If no year has been specified, an empty string is returned.
     */
    public String getExpirationYear() {
        return mExpiration.getTextField().getText();
    }

    /**
     * view in the card form
     *
     * @return the text in the cvv field
     */
    public String getCvv() {
        return mCvv.getText();
    }

    /**
     * view in the card form
     *
     * @return the text in the postal code field
     */
    public String getPostalCode() {
        return mPostalCode.getText();
    }

    /**
     * view in the card form
     *
     * @return the text in the country code field
     */
    public String getCountryCode() {
        return mCountryCode.getText().replaceAll("[^\\d]", "");
    }

    /**
     * view in the card form
     *
     * @return the unformatted text in the mobile number field
     */
    public String getMobileNumber() {
        return stripSeparators(mMobileNumber.getTextField().getText());
    }

    /**
     * Strips separators from a phone number string.
     *
     * @param phoneNumber phone number to strip.
     * @return phone string stripped of separators.
     */
    public String stripSeparators(String phoneNumber) {
        if (phoneNumber == null) {
            return null;
        }
        int len = phoneNumber.length();
        StringBuilder ret = new StringBuilder(len);

        for (int i = 0; i < len; i++) {
            char c = phoneNumber.charAt(i);
            int digit = Character.digit(c, 10);
            if (digit != -1) {
                ret.append(digit);
            } else if (isNonSeparator(c)) {
                ret.append(c);
            }
        }
        return ret.toString();
    }

    /**
     * isNonSeparator
     *
     * @param c char
     * @return isNonSeparator
     */
    public boolean isNonSeparator(char c) {
        return (c >= '0' && c <= '9') || c == '*' || c == '#' || c == '+'
                || c == WILD || c == WAIT || c == PAUSE;
    }

    /**
     * whether or not the save card CheckBox is checked
     *
     * @return whether or not the save card CheckBox is checked
     */
    public boolean isSaveCardCheckBoxChecked() {
        return mSaveCardCheckBox.isChecked();
    }

    @Override
    public void onTextUpdated(String s, int start, int before, int count) {
    }

    @Override
    public void onFocusChange(Component component, boolean hasFocus) {
        if (hasFocus && mOnCardFormFieldFocusedListener != null) {
            mOnCardFormFieldFocusedListener.onCardFormFieldFocused(component);
        }
    }
}